package com.hua.customviews.widget

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.drawable.BitmapDrawable
import android.os.Handler
import android.os.Looper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.TextView
import androidx.annotation.LayoutRes
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding
import androidx.viewpager.widget.ViewPager
import androidx.viewpager2.widget.ViewPager2
import com.hua.customviews.widget.ViewFilter.FilterType.IGNORE_SELF
import com.hua.customviews.widget.ViewFilter.FilterType.IGNORE_WITH_CHILD
import com.hua.customviews.widget.ViewFilter.FilterType.INVISIBLE

@Suppress("MemberVisibilityCanBePrivate", "Unused")
/**
 * 骨架屏布局
 * @author Hua
 * @date 2023年8月23日
 * @param mContext 可以获取LayoutInflater的Context，用于创建骨架屏View
 * @param sourceView 需要生成骨架屏的View
 * 采用View的替换来实现骨架屏的显示与隐藏 已适配RecyclerView
 * @see ViewReplacer
 *
 */
class SkeletonLayout(private val mContext: Context, private var sourceView: View) {
    var skeletonColor: Int = Color.parseColor("#4DA3A3A3")
    var skeletonRadius: Float = 4f.dp
    var textPlaceHolder: String = "sample text"
    var ignoreRoot = true
    var rvLayourId: Int = -1
    var rvItemCount: Int = 8
    var mLayoutManager: LinearLayoutManager? = null

    private val replacer: ViewReplacer = ViewReplacer(sourceView)
    private val startId: Int = sourceView.id
    private lateinit var skeletonView: View
    private val paint = Paint().apply {
        this.color = skeletonColor
        style = Paint.Style.FILL
    }

    private var mOnReadViewTreeListener: ((v: View) -> Unit)? = null
    private var mOnTypeMatchListener: ((v: View) -> Unit)? = null
    private var mOnShowListener: (() -> Unit)? = null
    private var mOnHideListener: (() -> Unit)? = null
    private val filters: MutableList<ViewFilter> = mutableListOf()
    private var typeToMatch: Any? = null
    private var postHandler: Handler? = Handler(Looper.myLooper()!!)
    private var isWaittingHide = false
    private var isAllPostDone = false
        set(value) {
            if (value != field) {
                field = value
                if (isWaittingHide) {
                    isWaittingHide = false
                    hide()
                }
            }
        }

    /**
     * 初始化生成骨架屏
     */
    fun inflateToSkeleton(@LayoutRes layout: Int): SkeletonLayout {
        if (sourceView is ViewGroup)
            this.skeletonView =
                if (startId == -1) {
                    (LayoutInflater.from(mContext)
                        .inflate(layout, null, false).rootView as ViewGroup)
                        .readToSkeleton(true)
                } else {
                    (LayoutInflater.from(mContext)
                        .inflate(layout, null, false).findViewById(startId) as ViewGroup)
                        .readToSkeleton(true)
                }
        return this
    }

    /**
     * 初始化生成骨架屏
     * @see inflateToSkeleton
     */
    fun <T : ViewBinding> inflateToSkeleton(bindingInflater: (LayoutInflater) -> T): SkeletonLayout {
        if (sourceView is ViewGroup)
            this.skeletonView =
                if (startId == -1) (bindingInflater.invoke(LayoutInflater.from(mContext)).root as ViewGroup)
                    .readToSkeleton(true)
                else (bindingInflater.invoke(LayoutInflater.from(mContext)).root.findViewById(startId) as ViewGroup)
                    .readToSkeleton(true)
        return this
    }

    /**
     * 设置View过滤器，过滤的View按照过滤规则处理
     * @see ViewFilter.FilterType
     */
    fun addFilterViews(vararg ids: Int): SkeletonLayout {
        filters.add(
            ViewFilter(ViewFilter.FilterType.IGNORE_WITH_CHILD, filteredId = mutableListOf<Int>().apply {
                addAll(ids.toTypedArray())
            })
        )
        return this
    }

    /**
     * @see addFilterViews
     */
    fun addFilterViews(vararg views: View): SkeletonLayout {
        filters.add(
            ViewFilter(ViewFilter.FilterType.IGNORE_WITH_CHILD, filteredView = mutableListOf<View>().apply {
                addAll(views)
            })
        )
        return this
    }

    /**
     * @see addFilterViews
     */
    fun addFilterViews(filterType: ViewFilter.FilterType, vararg ids: Int): SkeletonLayout {
        filters.add(
            ViewFilter(filterType, filteredId = mutableListOf<Int>().apply {
                addAll(ids.toTypedArray())
            })
        )
        return this
    }

    /**
     * @see addFilterViews
     */
    fun addFilterViews(filterType: ViewFilter.FilterType, vararg views: View): SkeletonLayout {
        filters.add(
            ViewFilter(filterType, filteredView = mutableListOf<View>().apply {
                addAll(views)
            })
        )
        return this
    }

    /**
     * @see addFilterViews
     */
    fun addFilterViews(filterType: ViewFilter.FilterType, viewType: Class<*>): SkeletonLayout {
        filters.add(
            ViewFilter(filterType, filteredClass = viewType)
        )
        return this
    }

    /**
     * 设置类型匹配监听
     */
    fun <T : View> setOnTypeMatches(type: Class<T>, listener: (v: View) -> Unit): SkeletonLayout {
        this.mOnTypeMatchListener = listener
        typeToMatch = type
        return this
    }

    /**
     * 设置View遍历listener，会invoke所有view
     */
    fun setOnReadViewTreeListener(listener: (v: View) -> Unit): SkeletonLayout {
        this.mOnReadViewTreeListener = listener
        return this
    }

    /**
     * 显示监听
     */
    fun setOnShowListener(listener: () -> Unit): SkeletonLayout {
        this.mOnShowListener = listener
        return this
    }

    /**
     * 隐藏监听
     */
    fun setOnHideListener(listener: () -> Unit): SkeletonLayout {
        this.mOnHideListener = listener
        return this
    }

    /**
     * 显示
     */
    fun show() {
        replacer.replace(skeletonView)
        mOnShowListener?.invoke()
    }

    /**
     * 显示一段时间后隐藏
     * @param delayHide 显示时间
     */
    fun show(delayHide: Long) {
        show()
        Handler(Looper.myLooper()!!).postDelayed(this::hide, delayHide)
    }

    /**
     * 隐藏
     */
    fun hide() {
        if (!isAllPostDone) isWaittingHide = true
        else replacer.restore()
        mOnHideListener?.invoke()
        //XLog.i("----->SkeletonLayout: hide!!!!")
    }

    /**
     * 将View读成骨架屏
     */
    @SuppressLint("NewApi")
    private fun <T : ViewGroup> T.readToSkeleton(isRoot: Boolean = false): ViewGroup {
        var canvas: Canvas? = null
        var bitMap: Bitmap? = null
        if (this is RecyclerView) this.replaceAdapter()
        if (this.isNeedAdapter()) return this
        //分发/处理骨架屏生成，listener/filter响应
        for (i in 0 until this.childCount) {
            val child = this.getChildAt(i)
            if (!child.isFiltered() || child.getFilter()!!.filterType != ViewFilter.FilterType.IGNORE_WITH_CHILD) {
                if (child is ViewGroup) child.readToSkeleton()
                if (child is RecyclerView) child.replaceAdapter()
                if ((child is TextView) && child.text.isEmpty()) child.text = textPlaceHolder
            }
            //类型匹配listener
            if (child.javaClass == typeToMatch) mOnTypeMatchListener?.invoke(child)
            //View树遍历listener
            mOnReadViewTreeListener?.invoke(child)
        }
        if (this.visibility == View.VISIBLE) this.visibility = View.INVISIBLE
        //骨架屏灰色块绘制过程
        this.post {
            if (this.visibility == View.INVISIBLE) this.visibility = View.VISIBLE
            if (this.measuredWidth <= 0
                || this.measuredHeight <= 0
                || (this.isFiltered() && this.getFilter()!!.filterType == ViewFilter.FilterType.IGNORE_WITH_CHILD)
            ) return@post
            if (bitMap == null || canvas == null) {
                bitMap = Bitmap.createBitmap(
                    this.measuredWidth,
                    this.measuredHeight,
                    Bitmap.Config.ARGB_8888
                )
//                XLog.i("----->SkeletonLayout: draw parent: ${this.getIdName()}")
                canvas = Canvas(bitMap!!)
                if (!this.isFiltered() && (!ignoreRoot || !isRoot)) {
                    canvas?.drawRoundRect(
                        0f,
                        0f,
                        this.measuredWidth.toFloat(),
                        this.measuredHeight.toFloat(),
                        skeletonRadius,
                        skeletonRadius,
                        paint
                    )
                }
            }

            for (i in 0 until this.childCount) {
                val child = this.getChildAt(i)
//                XLog.i("----->SkeletonLayout: ${child.getIdName()} $i ${child.javaClass.simpleName}")
                if (child.isNeedAdapter()) continue
                if (child !is ViewGroup) {
                    if (!child.isFiltered()) {
                        canvas?.drawRoundRect(
                            child.left.toFloat(),
                            child.top.toFloat(),
                            child.right.toFloat(),
                            child.bottom.toFloat(),
                            skeletonRadius,
                            skeletonRadius,
                            paint
                        )
                    }
//                    XLog.i("----->SkeletonLayout: draw child: ${child.getIdName()} ${child.left} ${child.top} ${child.right} ${child.bottom}")
                    if (child.visibility == View.VISIBLE && (!child.isFiltered() || child.getFilter()!!.filterType == ViewFilter.FilterType.INVISIBLE))
                        child.visibility = View.INVISIBLE
                }
            }
            this.foreground = BitmapDrawable(null, bitMap)
            postHandler?.removeCallbacksAndMessages(null)
            postHandler?.postDelayed({
                isAllPostDone = true
                postHandler = null
            }, 25)
        }
        return this
    }

    /**
     * 如果是需要Adapter的View
     */
    private fun View.isNeedAdapter(): Boolean {
        return (this is RecyclerView || this is ViewPager ||
                this is ViewPager2 || this is AdapterView<*>)
    }

    /**
     * 深拷贝一个View
     */
    @Deprecated("部分属性复制不成功")
    private fun <T : View> T.copy(): T {
        return try {
            this.javaClass.getConstructor(Context::class.java).newInstance(this.context).apply {
                this.javaClass.fields.forEach {
                    it.isAccessible = true
                    it.set(this, it.get(this@copy))
                }
            }
        } catch (e: Exception) {
            e.printStackTrace()
            this
        }
    }

    /**
     * 获取当前View的id名称
     */
    private fun <T : View> T.getIdName() = try {
        if (this.id == View.NO_ID) ""
        else resources.getResourceName(this.id).split(":id/")[1]
    } catch (e: Exception) {
        e.printStackTrace()
        ""
    }

    /**
     * 获取与本View相关的过滤器
     */
    private fun <T : View> T.getFilter(): ViewFilter? {
        filters.forEach { if (it.isFiltered(this)) return it }
        return null
    }

    /**
     * 判断View（根据id或类型）是否含有过滤器
     */
    private fun <T : View> T.isFiltered(): Boolean {
        return this.getFilter() != null
    }

    /**
     * 替换RV的Adapter
     */
    private fun <T : RecyclerView> T.replaceAdapter() {
//        XLog.i("----->SkeletonLayout: RV found")
        if (rvLayourId != -1 && rvItemCount > 0) {
            if (mLayoutManager == null) {
                this.layoutManager = LinearLayoutManager(mContext)
            } else {
                this.layoutManager = mLayoutManager
            }
            this.adapter = SkeletonAdapter(rvLayourId, rvItemCount)
        }
    }

    /**
     * 适用于RecyclerView的骨架屏Adapter
     * @param layout 需要转为骨架屏的item布局id
     * @param itemCount 生成的item数量
     */
    private inner class SkeletonAdapter(@LayoutRes private val layout: Int, private val itemCount: Int) :
        RecyclerView.Adapter<RecyclerView.ViewHolder>() {
        override fun getItemCount() = itemCount
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
            return object : RecyclerView.ViewHolder(
                (LayoutInflater.from(parent.context)
                    .inflate(layout, parent, false).rootView as ViewGroup).readToSkeleton(true)
            ) {}
        }

        override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {}
    }

    private inline val Float.dp: Float get() = this * mContext.resources.displayMetrics.density + 0.5f
}

/**
 * View匹配过滤器，目前支持按照View、ViewId、ViewClass进行匹配
 * @param filterType 设置的过滤策略，用于生成骨架屏时根据策略进行处理
 * @param filteredId 需要匹配过滤的ViewId
 * @param filteredView 需要匹配过滤的View
 * @param filteredClass 需要匹配过滤的ViewClass
 */
class ViewFilter(
    val filterType: FilterType = IGNORE_WITH_CHILD,
    private val filteredId: MutableList<Int> = mutableListOf(),
    private val filteredView: MutableList<View> = mutableListOf(),
    private val filteredClass: Class<*>? = null
) {
    /**
     * @property IGNORE_SELF 在生成骨架屏时只忽略自身
     * @property INVISIBLE 在生成骨架屏时，隐藏（ViewGroup不会生效）
     * @property IGNORE_WITH_CHILD 在生成骨架屏时忽略自身及其子View
     */
    enum class FilterType {
        IGNORE_SELF, INVISIBLE, IGNORE_WITH_CHILD
    }

    fun isFiltered(view: View): Boolean {
        return filteredId.any { it == view.id } || filteredView.any { it.id == view.id } || view.javaClass == filteredClass
    }

}

/**
 * View替换器，用于对目标View的替换和恢复
 * @author Hua
 * @date 2023年8月23日
 * @param sourceView 需要替换的View
 * @see replace
 * @see restore
 */
class ViewReplacer(private val sourceView: View) {
    private val sourceParent = sourceView.parent as? ViewGroup
    private val sourceId = sourceView.id
    private val sourceParams = sourceView.layoutParams
    private val sourceIndex = run {
        var index = -1
        sourceParent?.let {
            for (i in 0 until it.childCount) {
                if (sourceView == it.getChildAt(i)) {
                    index = i
                    break
                }
            }
        }
        index
    }
    private var currentView = sourceView

    fun replace(targetView: View) {
        (targetView.parent as? ViewGroup)?.removeView(targetView)
        targetView.id = sourceId
        sourceParent?.apply {
            removeView(currentView)
            addView(targetView, sourceIndex, sourceParams)
        }
        currentView = targetView
    }

    fun restore() {
        sourceParent?.apply {
            removeView(currentView)
            addView(sourceView, sourceIndex, sourceParams)
        }
        currentView = sourceView
    }

}
